package com.zl.oauth.server.config;

import com.zl.oauth.server.dto.CustomUserDetails;
import com.zl.oauth.server.dto.Role;
import com.zl.oauth.server.dto.User;
import com.zl.oauth.server.handler.MyAccessDeniedHandler;
import com.zl.oauth.server.handler.MyAuthExceptionEntryPoint;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.InMemoryTokenStore;

import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

@Configuration
@EnableResourceServer
public class OauthConfig  extends ResourceServerConfigurerAdapter {
    private static final Map<String, UserDetails> USER_SET = new HashMap<>();
    @Configuration
    @EnableAuthorizationServer
    protected static class OAuth2Config extends AuthorizationServerConfigurerAdapter {
        @Autowired
        UserDetailsService userDetailsService;
        @Autowired
        DefaultTokenServices defaultTokenServices;
        @Autowired
        private AuthenticationManager authenticationManager;
        @Autowired
        InMProvider inMProvider;
        @Bean
        DaoAuthenticationProvider daoAuthenticationProvider(){
            DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
            daoAuthenticationProvider.setPasswordEncoder(new BCryptPasswordEncoder());
            daoAuthenticationProvider.setUserDetailsService(userDetailsService);
            return daoAuthenticationProvider;
        }
        @Bean
        protected AuthenticationManager authenticationManager() throws Exception {
            //provider只有daoAuthenticationProvider会导致refresh_token不成功,因此增加inmemeryprovider
            ProviderManager authenticationManager = new ProviderManager(Arrays.asList(inMProvider,daoAuthenticationProvider()));
            //不擦除认证密码，擦除会导致TokenBasedRememberMeServices因为找不到Credentials再调用UserDetailsService而抛出UsernameNotFoundException
            authenticationManager.setEraseCredentialsAfterAuthentication(false);
            return authenticationManager;
        }

        @Autowired
        TokenStore tokenStore;
        @Bean
        public TokenStore tokenStore(){
            return new InMemoryTokenStore();
        }
        @Bean
        public DefaultTokenServices defaultTokenServices(){
            DefaultTokenServices d = new DefaultTokenServices();
            d.setTokenStore(tokenStore());
            d.setAuthenticationManager(authenticationManager);
            d.setSupportRefreshToken(true);
            d.setReuseRefreshToken(false);
            try {
                d.afterPropertiesSet();
            } catch (Exception e) {
                e.printStackTrace();
            }
            return d;
        }
        @Override
        public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
            endpoints.authenticationManager(authenticationManager).userDetailsService(userDetailsService).tokenServices(defaultTokenServices);
        }

        @Override
        public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
            security.checkTokenAccess("isAuthenticated()");
        }

        @Override
        public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
            // @formatter:off
            clients.inMemory()
                    .withClient("my-trusted-client")
                    .authorizedGrantTypes("password", "authorization_code", "refresh_token", "implicit")
                    .authorities("ROLE_CLIENT", "ROLE_TRUSTED_CLIENT").scopes("read", "write", "trust")
                    .resourceIds("oauth2-resource")
                    .accessTokenValiditySeconds(600).
                    redirectUris("https://anywhere")
                    .and()
                    .withClient("my-client-with-registered-redirect")
                    .authorizedGrantTypes("authorization_code")
                    .authorities("ROLE_CLIENT")
                    .scopes("read", "trust").resourceIds("oauth2-resource")
                    .redirectUris("https://anywhere?key=value").
                    and().withClient("my-client-with-secret")
                    .authorizedGrantTypes("client_credentials", "password").authorities("ROLE_CLIENT").scopes("read")
                    .resourceIds("oauth2-resource").secret("secret")
                    .and()
                    .withClient("free-web").secret("123456").authorizedGrantTypes("client_credentials","authorization_code","refresh_token")
                    .authorities("ROLE_CLIENT").accessTokenValiditySeconds(7200).refreshTokenValiditySeconds(7200)
                    .scopes("read","write").redirectUris("http://localhost:18091/client/fetchCode");
            // @formatter:on
        }

    }

    //Autowired作用是在加载时程序自动走到方法内部，参数会自动注入
    @Autowired
    public void initUserSet() throws Exception {
        if (USER_SET.size()==0) {
            USER_SET.put("user",new CustomUserDetails(new User("user", "password", Arrays.asList(new Role("USER"), new Role("ACTUATOR")))));
        }
    }
    @Bean
    public UserDetailsService userDetailsService() {
        return new UserDetailsService() {
            @Override
            public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
                return USER_SET.get(username);
            }
        };
    }

    @Autowired
    MyAccessDeniedHandler myAccessDeniedHandler;
    @Autowired
    MyAuthExceptionEntryPoint myAuthExceptionEntryPoint;
    @Override
    public void configure(ResourceServerSecurityConfigurer resources) {
        resources.resourceId("resource_id").stateless(false)
                .authenticationEntryPoint(myAuthExceptionEntryPoint)//zhangli2 point 认证失败自定义异常1
                .accessDeniedHandler(myAccessDeniedHandler);//zhangli point3 认证失败自定义异常2
    }
    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.httpBasic()
                .and().formLogin().loginPage("/authentication/require")//登录请求，需要自定义请求和页面。默认是/login
                //登录需要经过的url请求
                .loginProcessingUrl("/authentication/login")
                .and()
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
                .and()
                .authorizeRequests()
                .antMatchers("/auth/**").access("#oauth2.hasScope('read') or (!#oauth2.isOAuth() and hasRole('ROLE_USER'))")
                .antMatchers("/oauth/**").permitAll()
                .anyRequest().permitAll()//zhangli point1 允许放过哪些请求和拦截哪些请求（或者叫资源）
                .and()
                .csrf().disable();//zhangli point5 RESTful技术与CSRF(Cross-site request forgery跨站请求伪造)的冲突，CSRF默认不支持post请求
    }
}
